//
// Created by Administrator on 2020/6/17 0017.
//
#include "AESUtils.h"
#include <unistd.h>
#include <detect.h>
#include <common.h>

jstring
decryptByBytes(JNIEnv *jniEnv, jobject thiz, jstring password, jbyteArray content, jstring charset,
               jstring model, jbyteArray iv) {


    jstring strObj = NULL;

    jstring Cipher_clazz = NULL;
    jclass SecretKeySpec_clazz = NULL;
    jclass String_clazz = NULL;
    jclass IvParameterSpec_clazz = NULL;
    static jmethodID String_clazz_getBytes = NULL,
            Cipher_getInstance = NULL,
            Cipher_init = NULL,
            Cipher_init1 = NULL,
            Cipher_doFinal = NULL,
            SecretKeySpec_init = NULL,
            String_init = NULL,
            IvParameterSpec_init = NULL;

    IvParameterSpec_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/spec/IvParameterSpec");
    checkExc(jniEnv);


    Cipher_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/Cipher");
    checkExc(jniEnv);

    SecretKeySpec_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/spec/SecretKeySpec");
    checkExc(jniEnv);

    String_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
    checkExc(jniEnv);

    checkExc(jniEnv);
    if (String_init == NULL) {
        String_init = (*jniEnv)->GetMethodID(jniEnv, String_clazz, "<init>",
                                             "([BLjava/lang/String;)V");
        checkExc(jniEnv);
    }

    if (IvParameterSpec_init == NULL) {
        IvParameterSpec_init = (*jniEnv)->GetMethodID(jniEnv, IvParameterSpec_clazz, "<init>",
                                                      "([B)V");
        checkExc(jniEnv);
    }

    if (String_clazz_getBytes == NULL) {
        String_clazz_getBytes = (*jniEnv)->GetMethodID(jniEnv, String_clazz, "getBytes",
                                                       "(Ljava/lang/String;)[B");
        checkExc(jniEnv);
    }

    if (SecretKeySpec_init == NULL) {
        SecretKeySpec_init = (*jniEnv)->GetMethodID(jniEnv, SecretKeySpec_clazz,
                                                    "<init>",
                                                    "([BLjava/lang/String;)V");
        checkExc(jniEnv);
    }

    if (Cipher_getInstance == NULL) {
        Cipher_getInstance = (*jniEnv)->GetStaticMethodID(jniEnv, Cipher_clazz,
                                                          "getInstance",
                                                          "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
        checkExc(jniEnv);
    }


    if (Cipher_init == NULL) {
        Cipher_init = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz,
                                             "init",
                                             "(ILjava/security/Key;)V");
        checkExc(jniEnv);
    }

    if (Cipher_init1 == NULL) {
        Cipher_init1 = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz,
                                              "init",
                                              "(ILjava/security/Key;Ljava/security/AlgorithmParameters;)V");
        checkExc(jniEnv);
    }

    if (Cipher_doFinal == NULL) {
        Cipher_doFinal = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz, "doFinal", "([B)[B");
        checkExc(jniEnv);
    }


    jobject cipher = (*jniEnv)->CallStaticObjectMethod(jniEnv, Cipher_clazz, Cipher_getInstance,
                                                       model);


    jstring utf8 = (*jniEnv)->NewStringUTF(jniEnv, "UTF-8");
    jbyteArray array = (jbyteArray) (*jniEnv)->CallObjectMethod(jniEnv, password,
                                                                String_clazz_getBytes,
                                                                utf8);
    jstring aes = (*jniEnv)->NewStringUTF(jniEnv, "AES");
    jobject secretKeySpec = (*jniEnv)->NewObject(jniEnv, SecretKeySpec_clazz,
                                                 SecretKeySpec_init, array, aes);
    if (iv != NULL) {

        //IvParameterSpec ivPs = new IvParameterSpec(iv);
        jobject ivPs = (*jniEnv)->NewObject(jniEnv, IvParameterSpec_init, iv);

        //cipher.init(2, getKey(password), ivPs);
        (*jniEnv)->CallVoidMethod(jniEnv, cipher, Cipher_init1,
                                  2, secretKeySpec, ivPs);
    } else {

        //cipher.init(2, getKey(password));
        (*jniEnv)->CallVoidMethod(jniEnv, cipher, Cipher_init,
                                  2, secretKeySpec);
    }

//byte[] result = cipher.doFinal(content);
    jbyteArray result = (jbyteArray) (*jniEnv)->CallObjectMethod(jniEnv, cipher, Cipher_doFinal,
                                                                 content);
    checkExc(jniEnv);

//    LOGI(" the length of result is %d", (*jniEnv)->GetArrayLength(jniEnv, result));
//new String(result, charset);
    strObj = (jstring) (*jniEnv)->NewObject(jniEnv, String_clazz, String_init, result,
                                            charset);
    checkExc(jniEnv);

    (*jniEnv)->DeleteLocalRef(jniEnv, utf8);
    (*jniEnv)->DeleteLocalRef(jniEnv, aes);
    (*jniEnv)->DeleteLocalRef(jniEnv, secretKeySpec);

    (*jniEnv)->DeleteLocalRef(jniEnv, Cipher_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, SecretKeySpec_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, String_clazz);

    return strObj;
}

char *convertJByteArrayToChar(JNIEnv *env, jbyteArray byYUV1_) {

    jbyte *bytes = (*env)->GetByteArrayElements(env, byYUV1_, 0);
    int arrayLength = (*env)->GetArrayLength(env, byYUV1_);
    char *chars = malloc(arrayLength + 1);
    memset(chars, 0x0, arrayLength + 1);
    memcpy(chars, bytes, arrayLength);
    chars[arrayLength] = 0;

    LOGI("sss %s", chars);
    (*env)->ReleaseByteArrayElements(env, byYUV1_, bytes, 0);

    return chars;
}

jbyteArray test(JNIEnv *jniEnv, jobject thiz, jstring str) {

    /* if (jstringIsEmpty(jniEnv, str)) {
         LOGI("str is empty");
         throwIllegalArgumentException(jniEnv, "keyStr is null or \"\"");
     }
     jbyteArray array = jstring2jbyteArray(jniEnv, str);
     jstring ret = encode(jniEnv, array);
     char *ch = jstring2char(jniEnv, ret);

     LOGI("encode :: %s", ch);

     jbyteArray array1 = decode(jniEnv, ret);
     char *ch1 = jbyteArray2char(jniEnv, array1);

     LOGI("decode :: %s", ch1);
     free(ch1);
     free(ch);*/

    LOGI("1");
    reportRemote(jniEnv, KILL_APP_TRACE_PID);

    return NULL;
//    LOGI("encode :: %s", encode);
    return str;
}

jbyteArray
encryptToBytes(JNIEnv *jniEnv, jobject thiz, jstring password, jstring content, jstring charset,
               jstring model, jbyteArray iv) {

    jstring Cipher_clazz = NULL;
    jclass SecretKeySpec_clazz = NULL;
    jclass String_clazz = NULL;
    jclass IvParameterSpec_clazz = NULL;
    static jmethodID String_clazz_getBytes = NULL,
            Cipher_getInstance = NULL,
            Cipher_init = NULL,
            Cipher_init1 = NULL,
            Cipher_doFinal = NULL,
            SecretKeySpec_init = NULL,
            String_init = NULL,
            IvParameterSpec_init = NULL;


    IvParameterSpec_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/spec/IvParameterSpec");
    checkExc(jniEnv);

    Cipher_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/Cipher");
    checkExc(jniEnv);

    SecretKeySpec_clazz = (*jniEnv)->FindClass(jniEnv, "javax/crypto/spec/SecretKeySpec");
    checkExc(jniEnv);

    String_clazz = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
    checkExc(jniEnv);

    checkExc(jniEnv);
    String_init = (*jniEnv)->GetMethodID(jniEnv, String_clazz, "<init>", "([B)V");
    checkExc(jniEnv);

    if (IvParameterSpec_init == NULL) {
        IvParameterSpec_init = (*jniEnv)->GetMethodID(jniEnv, IvParameterSpec_clazz, "<init>",
                                                      "([B)V");
        checkExc(jniEnv);
    }

    if (String_clazz_getBytes == NULL) {
        String_clazz_getBytes = (*jniEnv)->GetMethodID(jniEnv, String_clazz, "getBytes",
                                                       "(Ljava/lang/String;)[B");
        checkExc(jniEnv);
    }

    if (SecretKeySpec_init == NULL) {
        SecretKeySpec_init = (*jniEnv)->GetMethodID(jniEnv, SecretKeySpec_clazz,
                                                    "<init>",
                                                    "([BLjava/lang/String;)V");
        checkExc(jniEnv);
    }

    if (Cipher_getInstance == NULL) {
        Cipher_getInstance = (*jniEnv)->GetStaticMethodID(jniEnv, Cipher_clazz,
                                                          "getInstance",
                                                          "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
        checkExc(jniEnv);
    }


    if (Cipher_init == NULL) {
        Cipher_init = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz,
                                             "init",
                                             "(ILjava/security/Key;)V");
        checkExc(jniEnv);
    }

    if (Cipher_init1 == NULL) {
        Cipher_init1 = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz,
                                              "init",
                                              "(ILjava/security/Key;Ljava/security/AlgorithmParameters;)V");
        checkExc(jniEnv);
    }

    if (Cipher_doFinal == NULL) {
        Cipher_doFinal = (*jniEnv)->GetMethodID(jniEnv, Cipher_clazz, "doFinal", "([B)[B");
        checkExc(jniEnv);
    }

    jstring utf8 = (*jniEnv)->NewStringUTF(jniEnv, "UTF-8");
    jbyteArray array = (jbyteArray) (*jniEnv)->CallObjectMethod(jniEnv, password,
                                                                String_clazz_getBytes,
                                                                utf8);
    jstring aes = (*jniEnv)->NewStringUTF(jniEnv, "AES");
    jobject secretKeySpec = (*jniEnv)->NewObject(jniEnv, SecretKeySpec_clazz,
                                                 SecretKeySpec_init, array, aes);


    // Cipher cipher = Cipher.getInstance(model);
    jobject cipher = (*jniEnv)->CallStaticObjectMethod(jniEnv, Cipher_clazz, Cipher_getInstance,
                                                       model);
    if (iv != NULL) {
        //IvParameterSpec ivPs = new IvParameterSpec(iv);
        jobject ivPs = (*jniEnv)->NewObject(jniEnv, IvParameterSpec_init, iv);

        //cipher.init(1, getKey(password), ivPs);
        (*jniEnv)->CallVoidMethod(jniEnv, cipher, Cipher_init1,
                                  1, secretKeySpec, ivPs);
    } else {
        //cipher.init(1, getKey(password));
        (*jniEnv)->CallVoidMethod(jniEnv, cipher, Cipher_init,
                                  1, secretKeySpec);
    }

    //cipher.doFinal(content.getBytes("UTF-8"));
    jbyteArray content_array = (jbyteArray) (*jniEnv)->CallObjectMethod(jniEnv, content,
                                                                        String_clazz_getBytes,
                                                                        utf8);
    jsize array_size = (*jniEnv)->GetArrayLength(jniEnv, content_array);
    LOGI("size is %d", array_size);
    jbyteArray result = (jbyteArray) (*jniEnv)->CallObjectMethod(jniEnv, cipher, Cipher_doFinal,
                                                                 content_array);

    jsize size = (*jniEnv)->GetArrayLength(jniEnv, result);
    LOGI("size is %d", size);

    (*jniEnv)->DeleteLocalRef(jniEnv, utf8);
    (*jniEnv)->DeleteLocalRef(jniEnv, aes);
    (*jniEnv)->DeleteLocalRef(jniEnv, secretKeySpec);

    (*jniEnv)->DeleteLocalRef(jniEnv, Cipher_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, SecretKeySpec_clazz);
    (*jniEnv)->DeleteLocalRef(jniEnv, String_clazz);

    return result;
}

jstring encrypt(JNIEnv *jniEnv, jobject thiz, jstring password, jstring content, jstring model,
                jbyteArray iv) {
    jstring ret = NULL;
    if ((*jniEnv)->MonitorEnter(jniEnv, thiz) == JNI_OK) {

        //Base64Utils.encode(encryptToBytes(password, content, "UTF-8", model, iv));
        char *charset = "UTF-8";
        jstring charset_str = char2jstring(jniEnv, charset);
        jbyteArray array = encryptToBytes(jniEnv, thiz, password, content, charset_str, model, iv);
        ret = encode(jniEnv, array);
        (*jniEnv)->DeleteLocalRef(jniEnv, charset_str);
        (*jniEnv)->MonitorExit(jniEnv, thiz);
    }
    return ret;
}

jstring decrypt(JNIEnv *jniEnv, jobject thiz, jstring password, jstring content, jstring model,
                jbyteArray iv) {

    jstring ret = NULL;
    if ((*jniEnv)->MonitorEnter(jniEnv, thiz) == JNI_OK) {
        //decryptByBytes(password, Base64Utils.decode(content), "UTF-8", model, iv);
        jbyteArray array = decode(jniEnv, content);
        char *charset = "UTF-8";
        jstring charset_str = char2jstring(jniEnv, charset);

        ret = decryptByBytes(jniEnv, thiz, password, array, charset_str, model, iv);

        (*jniEnv)->DeleteLocalRef(jniEnv, charset_str);

        (*jniEnv)->MonitorExit(jniEnv, thiz);
    }

    return ret;
}




